package org.example.genericreflect;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Slf4j
class ReflectUtil {

    public static void logTypeParameters(Class<?> cls) {
        log.info("---- type parameters of {} ----", cls.getSimpleName());
        TypeVariable<? extends Class<?>>[] typeParameters = cls.getTypeParameters();
        for (var p : typeParameters) {
            log.info("  > {}", p);
        }
        log.info("----");
    }

    public static void logInterfaces(Class<?> cls) {
        log.info("---- interfaces of {} ----", cls.getSimpleName());
        for (var f : getAllInterfaces(cls)) {
            log.info("  > {}", f);
            TypeVariable<? extends Class<?>>[] typeParameters = f.getTypeParameters();
            for (var p : typeParameters) {
                log.info("    > {}", p);
            }
        }
        log.info("----");
    }

    public static void logGenericInterfaces(Class<?> cls) {
        log.info("---- generic interfaces of {} ----", cls.getSimpleName());
        for (var f : getAllGenericInterfaces(cls)) {
            if (f instanceof ParameterizedType f1) {
                log.info("  > {}", f1);
            } else {
                log.info("  > {}", f);
            }
        }
        log.info("----");
    }

    public static List<Class<?>> getAllInterfaces(Class<?> cls) {
        Set<Class<?>> interfaces = new HashSet<>();
        internalGetAllInterfaces(interfaces, cls);
        return interfaces.stream().toList();
    }

    private static void internalGetAllInterfaces(Set<Class<?>> interfaces, Class<?> cls) {
        if (cls == null) {
            return;
        }
        Class<?>[] array = cls.getInterfaces();
        for (Class<?> f : array) {
            if (!interfaces.contains(f)) {
                interfaces.add(f);
                internalGetAllInterfaces(interfaces, f);
            }
        }
        internalGetAllInterfaces(interfaces, cls.getSuperclass());
    }

    public static List<Type> getAllGenericInterfaces(Class<?> cls) {
        Set<Type> genericInterfaces = new HashSet<>();
        internalGetAllGenericInterfaces(genericInterfaces, cls);
        return genericInterfaces.stream().toList();
    }

    private static void internalGetAllGenericInterfaces(Set<Type> genericInterfaces, Class<?> cls) {
        if (cls == null) {
            return;
        }
        Type[] array = cls.getGenericInterfaces();
        for (Type f : array) {
            genericInterfaces.add(f);
            if (f instanceof Class<?> c) {
                internalGetAllGenericInterfaces(genericInterfaces, c);
            }
        }
        internalGetAllGenericInterfaces(genericInterfaces, cls.getSuperclass());
    }

    public static void logGenericInterface(Type g) {

        log.info("---- actual type parameters of {} ----", g);
        if (g instanceof ParameterizedType g1) {
            Class<?> rawType = (Class<?>) g1.getRawType();
            TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
            Type[] typeArguments = g1.getActualTypeArguments();
            for (int i = 0; i < typeParameters.length; i++) {
                TypeVariable<?> type2 = typeParameters[i];
                Class<?> type1 = (Class<?>) typeArguments[i];
                log.info("     {}> {}: {}", type1, i, type2);
            }
        }
        log.info("----");
    }

    public static void logMethods(Class<?> proxyClass) {
        Method[] methods = proxyClass.getMethods();
        log.info("---- methods of {} ----", proxyClass);
        for (Method m1 : methods) {
            log.info("  - {}", m1);
        }
        log.info("----");
    }
}
